package net.lqy.aipan.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import net.lqy.aipan.component.StorageEngine;
import net.lqy.aipan.config.MinioConfig;
import net.lqy.aipan.dto.AccountDTO;
import net.lqy.aipan.dto.AccountFileDTO;
import net.lqy.aipan.dto.FolderTreeNodeDTO;
import net.lqy.aipan.enums.BizCodeEnum;
import net.lqy.aipan.enums.DeletedFlagEnum;
import net.lqy.aipan.enums.FileTypeEnum;
import net.lqy.aipan.enums.FolderFlagEnum;
import net.lqy.aipan.exception.BizException;
import net.lqy.aipan.interceptor.LoginInterceptor;
import net.lqy.aipan.mapper.AccountFileMapper;
import net.lqy.aipan.mapper.FileMapper;
import net.lqy.aipan.model.AccountFileDO;
import net.lqy.aipan.model.FileDO;
import net.lqy.aipan.request.*;
import net.lqy.aipan.service.AccountFileService;
import net.lqy.aipan.service.StorageService;
import net.lqy.aipan.util.CommonUtil;
import net.lqy.aipan.util.SpringBeanUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AccountFileServiceImpl implements AccountFileService {
    @Autowired
    private AccountFileMapper accountFileMapper;
    @Autowired
    private StorageEngine storageEngine;
    @Autowired
    private MinioConfig minioConfig;
    @Autowired
    private FileMapper fileMapper;
    @Autowired
    private StorageService storageService;

    @Override
    public List<AccountFileDTO> listFile(Long accountId, Long parentId) {
        List<AccountFileDO> fileDOS = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                .eq("account_id", accountId)
                .eq("parent_id", parentId)
                .orderByDesc("is_dir")
                .orderByDesc("gmt_create"));
        List<AccountFileDTO> fileDTOS = SpringBeanUtil.copyProperties(fileDOS, AccountFileDTO.class);
        return fileDTOS;
    }

    /**
     * 文件重命名
     *
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void renameFile(FileRenameReq request) {
        AccountDTO loginUser = LoginInterceptor.threadLocal.get();
        AccountFileDO accountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>()
                .eq("account_id", loginUser.getId())
                .eq("id", request.getId()));
        if (accountFileDO == null) {
            log.error("文件不存在");
            throw new BizException(BizCodeEnum.FILE_NOT_EXISTS);
        } else {
            if (request.getFileName().equals(accountFileDO.getFileName())) {
                log.error("文件名重复修改");
                throw new BizException(BizCodeEnum.FILE_NAME_REPEAT);
            } else {
                //检查当前目录下的文件是否名字重复
                List<AccountFileDO> fileDOS = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                        .eq("parent_id", accountFileDO.getParentId())
                        .eq("account_id", loginUser.getId())
                        .eq("file_name", request.getFileName())
                        .eq("del", DeletedFlagEnum.EXISTED.getCode()));
                if (fileDOS.size() > 0) {
                    log.error("文件名重复修改");
                    throw new BizException(BizCodeEnum.FILE_NAME_REPEAT);
                } else {
                    //没有和其他文件重复
                    accountFileDO.setFileName(request.getFileName());
                    accountFileMapper.updateById(accountFileDO);
                }

            }
        }
    }

    /**
     * 文件树
     *
     * @param accountId
     * @param folderId
     * @return
     */
    @Override
    public List<FolderTreeNodeDTO> folderTree(Long accountId, long folderId) {
        // 查询当前用户下所有未删除的文件夹
        List<AccountFileDO> accountFileDOS = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                .eq("account_id", accountId)
//                .eq("parent_id", parentId)
                .eq("is_dir", FolderFlagEnum.FOLDER.getCode())
                .eq("del", DeletedFlagEnum.EXISTED.getCode()));
        if (CollectionUtils.isNotEmpty(accountFileDOS)) {
            //构建map，key为id，value为节点
            Map<Long, FolderTreeNodeDTO> nodeMap = accountFileDOS.stream()
                    .collect(Collectors.toMap(AccountFileDO::getId, accountFileDO -> {
                        return FolderTreeNodeDTO.builder()
                                .id(accountFileDO.getId())
                                .parentId(accountFileDO.getParentId())
                                .fileName(accountFileDO.getFileName())
                                .children(new ArrayList<>())
                                .build();
                    }));

            //  构建树结构： 遍历每个节点，将其添加到对应父节点的 children 列
            nodeMap.forEach((id, node) -> {
                Long nodeParentId = node.getParentId();
                // 如果不是目标目录，并且父节点存在，则将当前节点加入父节点的 children 列表
                if (nodeParentId != null && nodeMap.containsKey(nodeParentId)) {
                    nodeMap.get(nodeParentId).getChildren().add(node);
                }
            });
            log.info("fileTree:{}", nodeMap);
            // 返回指定 folderId 下的所有子节点
            return nodeMap.values().stream().filter(node -> node.getParentId() == folderId).collect(Collectors.toList());
        }
        return List.of();
    }


    /**
     * 获取文件树结构
     * V2
     *
     * @param accountId
     * @param folderId
     * @return
     */
    @Override
    public List<FolderTreeNodeDTO> folderTreeV2(Long accountId, long folderId) {
        List<AccountFileDO> accountFileDOS = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                .eq("account_id", accountId)
//                .eq("parent_id", parentId)
                .eq("is_dir", FolderFlagEnum.FOLDER.getCode())
                .eq("del", DeletedFlagEnum.EXISTED.getCode()));
        if (CollectionUtils.isNotEmpty(accountFileDOS)) {
            List<FolderTreeNodeDTO> nodeDTOList = accountFileDOS.stream().map(accountFileDO -> {
                return FolderTreeNodeDTO.builder()
                        .id(accountFileDO.getId())
                        .parentId(accountFileDO.getParentId())
                        .fileName(accountFileDO.getFileName()).build();
            }).collect(Collectors.toList());

            //根据parentId分组
            Map<Long, List<FolderTreeNodeDTO>> map = nodeDTOList.stream()
                    .collect(Collectors.groupingBy(FolderTreeNodeDTO::getParentId));

            //构建树结构:遍历每个节点，将它的子节点加入到 children 字段中
            for (FolderTreeNodeDTO nodeDTO : nodeDTOList) {
                List<FolderTreeNodeDTO> children = map.get(nodeDTO.getId());
                if (CollectionUtils.isNotEmpty(children)) {
                    nodeDTO.setChildren(children);
                }
            }
            // 返回指定 folderId 下的所有子节点
            return nodeDTOList.stream()
                    .filter(nodeDTO -> nodeDTO.getParentId() == folderId).collect(Collectors.toList());
        }
        return List.of();
    }

    /**
     * 普通小文件上传
     * 1、上传到存储引擎
     * 2、构建文件信息
     * 3、保存用户文件关系
     *
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void uploadFile(FileUploadReq request) {
        AccountDTO loginUser = LoginInterceptor.threadLocal.get();
        storageService.assertStorageEnough(loginUser.getId(), request.getFile().getSize());
        //1、上传到存储引擎
        String objectKey = storeFile(request);
        //2、构建文件信息
        FileDO fileDO = saveFile(objectKey, request);
        //3、保存用户文件关系
        AccountFileDTO accountFileDTO = AccountFileDTO.builder()
                .accountId(fileDO.getAccountId())
                .fileId(fileDO.getId())
                .parentId(request.getParentId())
                .fileName(fileDO.getFileName())
                .isDir(FolderFlagEnum.FILE.getCode())
                .fileSize(fileDO.getFileSize())
                .fileSuffix(fileDO.getFileSuffix())
                .build();
        String fileTypeName = getFileTypeName(fileDO);
        accountFileDTO.setFileType(fileTypeName);
        AccountFileDO accountFileDO = saveAccountFile(accountFileDTO);
        storageService.updateByStorageFile(accountFileDO);
    }

    /**
     * 批量移动文件
     * 1、检查被移动的id是否合法
     * 2、检查目标文件夹是否存在而且不能是被移动的文件夹的子文件夹
     * 3、检查目标文件夹下是否有同名文件或文件夹
     * 4、移动文件
     *
     * @param request
     */
    @Override
    public void moveBatch(FileBatchReq request) {
        //  检查被移动的id是否合法
        Long accountId = LoginInterceptor.threadLocal.get().getId();
        List<Long> fileIds = request.getFileIds().stream().distinct().collect(Collectors.toList());
        List<AccountFileDO> list = checkFileIdsLegal(accountId, fileIds);

        //  检查目标文件夹是否合法
        checkTargetFolderLegal(accountId, request.getTargetParentId(), list);

        //  移动文件
        list.forEach(this::checkFileNameRepeat);
        int affected = accountFileMapper.update(new UpdateWrapper<AccountFileDO>()
                .set("parent_id", request.getTargetParentId())
                .in("id", fileIds)
                .eq("account_id", accountId));
        if (affected != fileIds.size()) {
            throw new BizException(BizCodeEnum.FILE_MOVE_BATCH_FAIL);
        }

    }

    /**
     * 批量删除文件
     *
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delBatch(FileBatchReq request) {
        Long accountId = LoginInterceptor.threadLocal.get().getId();
        //  检查文件id
        List<AccountFileDO> accountFileDOS = checkFileIdsLegal(accountId, request.getFileIds());
        // 查找删除的所有文件
        ArrayList<AccountFileDO> delAccountFileDOS = new ArrayList<>();
        findAllAccountFileDOListWithRecur(accountFileDOS, delAccountFileDOS, accountId, true);
        // 更新存储空间
        storageService.updateByDelFile(delAccountFileDOS);
        // 批量删除
        List<Long> batchIds = delAccountFileDOS.stream().map(accountFileDO -> accountFileDO.getId()).collect(Collectors.toList());
        accountFileMapper.deleteBatchIds(batchIds);
    }

    /**
     * 批量复制文件
     *
     * 检查被转移的文件ID是否合法
     * 检查目标文件夹ID是否合法
     * 执行拷贝，递归查找【差异点，ID是全新的】
     * 计算存储空间大小，检查是否足够【差异点，空间需要检查】
     * 存储相关记录
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void copyBatch(FileBatchReq request) {
        Long accountId = LoginInterceptor.threadLocal.get().getId();
        List<Long> fileIds = request.getFileIds().stream().distinct().collect(Collectors.toList());
        //  检查被转移的文件ID是否合法
        List<AccountFileDO> list = checkFileIdsLegal(accountId, fileIds);
        //  检查目标文件夹ID是否合法
        checkTargetFolderLegal(accountId, request.getTargetParentId(), list);
        // 存储空间大小更新
        storageService.updateByCopyBatchFile(list);
        // 执行拷贝
        List<AccountFileDO> newAccountFileDOList=findBatchCopyFileWithRecur(list, request.getTargetParentId());
        accountFileMapper.insertBatch(newAccountFileDOList);
    }

    /**
     * 递归查找并复制一批文件到目标父目录下
     * 此方法用于处理文件的批量复制操作，它会递归地遍历源文件列表，并将这些文件复制到指定的目标父目录中
     * 主要用于文件管理场景中的批量操作功能
     *
     * @param sourceList 源文件列表，包含了一批待复制的文件对象
     * @param targetParentId 目标父目录的ID，指定了文件将被复制到的目录位置
     * @return 返回一个包含已复制文件的列表，如果复制失败或源列表为空，则可能返回null
     */
    private List<AccountFileDO> findBatchCopyFileWithRecur(List<AccountFileDO> sourceList, Long targetParentId) {
        if (sourceList == null || sourceList.isEmpty()) {
            return null;
        }
        ArrayList<AccountFileDO> newAccountFileDOList = new ArrayList<>();
        sourceList.forEach(accountFileDO -> doCopy(accountFileDO, targetParentId, newAccountFileDOList));
        return newAccountFileDOList;
    }

    /**
     * 复制文件
     * @param accountFileDO
     * @param targetParentId
     * @param newAccountFileDOList
     */
    private void doCopy(AccountFileDO accountFileDO, Long targetParentId, ArrayList<AccountFileDO> newAccountFileDOList) {
        Long accountFileDOId = accountFileDO.getId();
        accountFileDO.setId(IdUtil.getSnowflakeNextId());
        accountFileDO.setParentId(targetParentId);
        accountFileDO.setGmtCreate(null);
        accountFileDO.setGmtModified(null);

        //处理重复
        checkFileNameRepeat(accountFileDO);

        //存储
        newAccountFileDOList.add(accountFileDO);

        if(accountFileDO.getIsDir() == FolderFlagEnum.FOLDER.getCode()){
            List<AccountFileDO> accountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                    .eq("account_id", accountFileDO.getAccountId())
                    .eq("parent_id", accountFileDOId));
            findBatchCopyFileWithRecur(accountFileDOList, accountFileDOId);
        }
    }

    /**
     * 1、不能是文件要是文件夹
     * 2、不能是被移动的文件夹的子文件夹
     *
     * @param accountId
     * @param targetParentId
     * @param list
     */
    private void checkTargetFolderLegal(Long accountId, Long targetParentId, List<AccountFileDO> list) {
        AccountFileDO accountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>()
                .eq("account_id", accountId)
                .eq("id", targetParentId)
                .eq("is_dir", FolderFlagEnum.FOLDER.getCode()));
        if (accountFileDO == null) {
            log.error("目标文件夹不存在");
            throw new BizException(BizCodeEnum.FILE_TARGET_PARENT_NO_EXIST);
        } else {
            //要操作的目标文件列表不能包含目标文件夹id
            List<AccountFileDO> allAccountFileDOList = new ArrayList<>();
            // 递归查询出所有文件
            findAllAccountFileDOListWithRecur(list, allAccountFileDOList, accountId, true);

            if (allAccountFileDOList.contains(accountFileDO)) {
                log.error("目标文件夹选择错误");
                throw new BizException(BizCodeEnum.FILE_TARGET_PARENT_ILLEGAL);
            }
        }
    }


    /**
     * 递归地查找所有账户文件对象列表
     * 如果onlyFolder为true，则只查找文件夹类型
     *
     * @param currentLevelList                 当前层级的账户文件对象列表
     * @param allAccountFileDOList 累积的所有账户文件对象列表
     * @param accountId            账户ID
     * @param onlyFolder           是否仅查找文件夹类型的账户文件对象
     */
    private void findAllAccountFileDOListWithRecur(List<AccountFileDO> currentLevelList, List<AccountFileDO> allAccountFileDOList, Long accountId, boolean onlyFolder) {
        // 遍历当前层级的账户文件对象列表
        for (AccountFileDO accountFileDO : currentLevelList) {
            if (accountFileDO.getIsDir() == FolderFlagEnum.FOLDER.getCode()) {
                List<AccountFileDO> fileDOS = null;
                if (onlyFolder) {
                    fileDOS = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                            .eq("parent_id", accountFileDO.getId())
                            .eq("account_id", accountId)
                            .eq("is_dir", FolderFlagEnum.FOLDER.getCode()));
                } else {
                    fileDOS = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                            .eq("parent_id", accountFileDO.getId())
                            .eq("account_id", accountId));
                }
                if (CollectionUtils.isNotEmpty(fileDOS)) {
                    allAccountFileDOList.addAll(fileDOS);
                    findAllAccountFileDOListWithRecur(fileDOS, allAccountFileDOList, accountId, onlyFolder);
                }
            }
        }
    }


    /**
     * 检查文件ID是否合法
     * @param accountId
     * @param fileIds
     * @return
     */
    private List<AccountFileDO>  checkFileIdsLegal(Long accountId, List<Long> fileIds) {
        List<AccountFileDO> list = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
                .in("id", fileIds)
                .eq("account_id", accountId));
        if (CollectionUtils.isEmpty(list) || list.size() != fileIds.size()) {
            log.error("文件不合法数量：{}", fileIds.size() - list.size());
            throw new BizException(BizCodeEnum.FILE_AUTH_FAIL);
        }
        return list;
    }

    /**
     * 获取文件类型名称
     */
    private String getFileTypeName(FileDO fileDO) {
        String fileTypeName = accountFileMapper.selectFileTypeNameBySuffix(fileDO.getFileSuffix());
        if (StringUtils.isNotBlank(fileTypeName)) {
            return fileTypeName;
        }
        return FileTypeEnum.OTHER.getName();
    }

    /**
     * 保存文件信息
     *
     * @param objectKey
     * @param request
     */
    private FileDO saveFile(String objectKey, FileUploadReq request) {
        FileDO fileDO = new FileDO();
        fileDO.setAccountId(LoginInterceptor.threadLocal.get().getId());
        fileDO.setFileName(request.getFileName());
        fileDO.setFileSuffix(CommonUtil.getFileSuffix(request.getFileName()));
        fileDO.setFileSize(request.getFile().getSize());
        fileDO.setObjectKey(objectKey);
        fileDO.setIdentifier(request.getIdentifier());
        fileMapper.insert(fileDO);
        return fileDO;
    }

    /**
     * 存储文件
     *
     * @param request
     * @return
     */
    private String storeFile(FileUploadReq request) {
        String objectKey = CommonUtil.getFilePath(request.getFileName());
        storageEngine.upload(minioConfig.getBucketName(), objectKey, request.getFile());
        return minioConfig.getEndpoint() + "/" + minioConfig.getAvatarBucketName() + "/" + objectKey;
    }

    @Override
    public void createFolder(FolderCreateReq request) {
        AccountDTO loginUser = LoginInterceptor.threadLocal.get();
        if (loginUser == null) {
            //注册时被调用来创建根目录
            loginUser = new AccountDTO();
            loginUser.setId(request.getAccountId());
        } else {
            AccountFileDTO accountFileDTO = AccountFileDTO.builder()
                    .accountId(loginUser.getId())
                    .parentId(request.getParentId())
                    .isDir(FolderFlagEnum.FOLDER.getCode())
                    .fileName(request.getFolderName())
                    .fileType("folder")
                    .fileSize(0L)
                    .build();

            AccountFileDO accountFileDO = saveAccountFile(accountFileDTO);
        }
    }

    /**
     * 1、检查父文件夹是否存在
     * 2、检查文件名是否重复
     * 3、保存文用户文件信息
     *
     * @param accountFileDTO
     * @return
     */
    private AccountFileDO saveAccountFile(AccountFileDTO accountFileDTO) {
        AccountFileDO accountFileDO = SpringBeanUtil.copyProperties(accountFileDTO, AccountFileDO.class);

        // 检查父文件夹是否存在
        checkParentFileExists(accountFileDO);

        //  检查文件名是否重复
        checkFileNameRepeat(accountFileDO);

        //  保存用户文件信息
        if (accountFileDO.getIsDir() == FolderFlagEnum.FILE.getCode()) {
            //文件类型就设置后缀
            accountFileDO.setFileSuffix(accountFileDO.getFileName().substring(accountFileDO.getFileName().lastIndexOf(".") + 1));
        }
        accountFileMapper.insert(accountFileDO);
        return accountFileDO;

    }

    /**
     * 检查父文件夹是否存在
     */
    private void checkParentFileExists(AccountFileDO accountFileDO) {
        AccountFileDO parentFolder = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>()
                .eq("account_id", accountFileDO.getAccountId())
                .eq("id", accountFileDO.getParentId()));
        if (parentFolder == null) {
            throw new BizException(BizCodeEnum.FILE_TARGET_PARENT_NO_EXIST);
        }

    }

    /**
     * 检查文件名是否重复
     */
    private void checkFileNameRepeat(AccountFileDO accountFileDO) {
        AccountFileDO existing = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>()
                .eq("account_id", accountFileDO.getAccountId())
                .eq("parent_id", accountFileDO.getParentId())
                .eq("file_name", accountFileDO.getFileName())
                .eq("is_dir", accountFileDO.getIsDir()));

        if (existing != null) {
            if (existing.getIsDir() == FolderFlagEnum.FOLDER.getCode()) {
                accountFileDO.setFileName(accountFileDO.getFileName() + "(1)");
            } else {
                String[] split = accountFileDO.getFileName().split("\\.");
                accountFileDO.setFileName(split[0] + "(1)." + split[1]);
            }
        }
    }


}
